namespace ScrumSprintMonitor.Implementation
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Linq;

	using Infrastructure;
	using Infrastructure.IoC;

	using Microsoft.Practices.ServiceLocation;
	using Microsoft.Practices.Unity;

    #endregion

	public class UnityContainerAbstraction : ServiceLocatorImplBase,
	                                           IDisposable
	{
		#region Private Fields

		private static readonly Dictionary<Type, bool> registeredModuleTypesDictionary = new Dictionary<Type, bool>();
		private static bool configurationDependentModulesAreSetUp;
		private static UnityContainerAbstraction containerAbstraction;
        private readonly IUnityContainer container;

		#endregion

		static UnityContainerAbstraction()
		{
			ServiceLocator.SetLocatorProvider(EnsureContainerIsSetUp);
		}

		public UnityContainerAbstraction(IUnityContainer container)
		{
            this.container = container;
		    this.container.RegisterInstance<IServiceLocator>(this);

			LoadRegisteredModules(false);
		}

		public IUnityContainer Instance
		{
            get { return this.container; }
		}

		#region Implementation of IDisposable

		public void Dispose()
		{
			if (containerAbstraction != null)
			{
                this.container.Dispose();

				containerAbstraction = null;
			}
		}

		#endregion

		public void LoadRegisteredModules(bool configurationDependent)
		{
			var moduleList = new List<IModule>();
			var selector = registeredModuleTypesDictionary.Where(kvp => kvp.Value == configurationDependent);

			selector.Select(kvp => kvp.Key).ToList().ForEach(type => moduleList.Add((IModule) Activator.CreateInstance(type)));
            moduleList.ForEach(module => this.container.Load(module));

			selector.ToList().ForEach(kvp => registeredModuleTypesDictionary.Remove(kvp.Key));
		}

		private void LoadConfigurationDependentModules()
		{
			LoadRegisteredModules(true);
		}

        public static UnityContainerAbstraction EnsureContainerIsSetUp()
        {
            return containerAbstraction ?? (containerAbstraction = new UnityContainerAbstraction(new UnityContainer()));
        }

	    public static UnityContainerAbstraction EnsureContainerIsSetUp(IUnityContainer container)
		{
            Debug.Assert(containerAbstraction == null || containerAbstraction.Instance == container);

            return containerAbstraction ?? (containerAbstraction = new UnityContainerAbstraction(container));
		}

		public static void EnsureConfigurationDependentModulesAreSetUp()
		{
		    EnsureContainerIsSetUp();

			if (!configurationDependentModulesAreSetUp)
			{
				configurationDependentModulesAreSetUp = true;

				containerAbstraction.LoadConfigurationDependentModules();
			}
		}

		public static void RegisterModule<T>()
		{
			RegisterModule<T>(false);
		}

		public static void RegisterModule<T>(bool configurationDependent)
		{
			registeredModuleTypesDictionary.Add(typeof(T), configurationDependent);
		}

		/// <summary>
		///   When implemented by inheriting classes, this method will do the actual work of resolving
		///   the requested service instance.
		/// </summary>
		/// <param name = "serviceType">Type of instance requested.</param>
		/// <param name = "key">Name of registered service you want. May be null.</param>
		/// <returns>The requested service instance.</returns>
		protected override object DoGetInstance(Type serviceType, string key)
		{
			Debug.Assert(key == null);

			object service;

			try
			{
				if (serviceType == typeof(IServiceProvider))
				{
					return this;
				}

                service = this.container.Resolve(serviceType);
			}
            catch (ResolutionFailedException e)
			{
                this.container.Resolve<ILogger<UnityContainerAbstraction>>().Warn(e, "Exception detected while activating service {0}.", serviceType.ToString());

				throw;
			}

			return service;
		}

		/// <summary>
		///   When implemented by inheriting classes, this method will do the actual work of
		///   resolving all the requested service instances.
		/// </summary>
		/// <param name = "serviceType">Type of service requested.</param>
		/// <returns>Sequence of service instance objects.</returns>
		protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
		{
		    return this.container.ResolveAll(serviceType);
		}
	}
}